using CodeSmith.Engine;
using SchemaExplorer;
using System;
using System.Windows.Forms.Design;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Diagnostics;

namespace SNARF
{
	public partial class BaseTemplate : CodeTemplate
	{
		private DatabaseSchema _sourceDatabase;
		[Category("Database")]
		[Description("The datasource.")]
		[NotChecked]
		public DatabaseSchema SourceDatabase
		{
			get
			{
				
					return this._sourceDatabase;
				
			}
			set
			{
				this._sourceDatabase = value;
			}
		}
		
		private TableSchemaCollection _sourceTables;
		[Category("Database")]
		[Description("The tables you wish to generate, if you do not select any SourceTables or SourceViews, and SourceDatabase is completed, then the entire database will be generated.")]
		[Optional, NotChecked]
		public TableSchemaCollection SourceTables
		{
			get
			{
			
					return this._sourceTables;
				
			}
			set
			{
				this._sourceTables = value;
			}
		}	
		
/* TODO
		
		private ViewSchemaCollection _sourceViews;
		[Category("Database")]
		[Description("The views you wish to generate, if you do not select any SourceViews or SourceTables, and SourceDatabase is completed, then the entire database will be generated.")]
		[Optional, NotChecked]
		public ViewSchemaCollection SourceViews
		{
			get
			{
			
					return this._sourceViews;
			}	
			set
			{
				this._sourceViews = value;
			}
		}	
*/
		
		
		private string _outputDirectory = "";
		[Editor(typeof(System.Windows.Forms.Design.FolderNameEditor), typeof(System.Drawing.Design.UITypeEditor))] 
		[Optional, NotChecked]
		[Category("Output")]
		[Description("The directory to output the results to.")]
		[DefaultValue("")]
		public string OutputDirectory 
		{ 
			get
			{
				if (this._outputDirectory == string.Empty)
					return @"C:\CAR\" + (SourceDatabase != null ? SourceDatabase.Name : "");
				else
					return _outputDirectory;
			}
			set
			{
				if (value.EndsWith("\\")) value = value.Substring(0, value.Length - 1);
				_outputDirectory = value;
			} 
		}	
		
		private string _classNamespace = "";		
		[Optional, NotChecked]
		[Category("Output")]
		[Description("The namespace that the generated business objects should be in.")]
		[DefaultValue("")]
		public string ClassNamespace 
		{ 
			get
			{
				if (this._classNamespace == string.Empty)
					return "Company.Project.Business";
				else
					return _classNamespace;
			}
			set
			{				
				_classNamespace = value;
			} 
		}
		
		private string _assemblyName = "";		
		[Optional, NotChecked]
		[Category("Output")]
		[Description("The name of the assembly the generated business objects should be in. Defaults to ClassNamespace")]
		[DefaultValue("")]
		public string AssemblyName 
		{ 
			get
			{
				if (this._assemblyName == string.Empty)
					return this.ClassNamespace;
				else
					return _assemblyName;
			}
			set
			{				
				_assemblyName = value;
			} 
		}
		
		private bool _stripID = true;
		[Optional, NotChecked]
		[Category("Output")]
		[Description("Specify whether for foreign key relationships that become properties, to remove ID from the column name (so OrderLines.OrderIDObject becomes OrderLines.OrderObject) ")]
		[DefaultValue(true)]
		public bool StripID 
		{ 
			get
			{
				return _stripID;
			}
			set
			{				
				_stripID = value;
			} 
		}		
		
		
		
		public string GetAliasVariableType(Type systemType)
		{
			string type = systemType.ToString();
			
			switch (type)
			{
				case "System.String": return "string";
				case "System.Byte": return "byte";
				case "System.Byte[]": return "byte[]";			
				case "System.Int16": return "short";
				case "System.Int32": return "int";
				case "System.Int64": return "long";
				case "System.Char": return "char";
				case "System.Single": return "float";
				case "System.Double": return "double";
				case "System.Boolean": return "bool";
				case "System.Decimal": return "decimal";
				case "System.SByte": return "sbyte";
				case "System.UInt16": return "ushort";
				case "System.UInt32": return "uint";
				case "System.UInt64": return "ulong";
				case "System.Object": return "object";
				default:
					// remove System.
					string nameSpace = GetNamespace(type);				
					if (nameSpace == "System")
						type = GetClassNameForVariable(type);
						
					return type;
			}
		}	
		
		public string GetClassNameForVariable(string name)
		{
			string[] namespaces = name.Split(new Char[] {'.'});
			return namespaces[namespaces.Length-1];
		}
		
		
		public string GetNamespace(string name)
		{
			string[] namespaces = name.Split(new Char[] {'.'});
			return String.Join(".", namespaces, 0, namespaces.Length-1);		
		}	
		
		public bool IsColumnNullable(ColumnSchema column)
		{
		
			if (column.AllowDBNull && column.SystemType.IsValueType)
				return true;
			else
				return false;		
		}	
		
		public string GetColumnType(ColumnSchema column)
		{			
			return GetColumnType(column, true);
		}
		
		public string GetColumnType(ColumnSchema column, bool addNullable)
		{
			string colType = this.GetAliasVariableType( column.SystemType );
			
			if (this.IsColumnNullable(column) && addNullable )
				colType = string.Format("Nullable<{0}>", colType);
				
			return colType;
		}		
		
		public string GetFieldName(ColumnSchema column)
		{
			return GetFieldName(column.Name);
			
		}
		
		public string GetFieldName(String name)
		{			
			return "_" + StringUtil.ToCamelCase(name);
		}
		
		public string GetPropertyName(ColumnSchema column)
		{
			return GetPropertyName(column.Name);
		}
		
		public string GetPropertyName(string name)
		{
			return StringUtil.ToPascalCase(name);
		}
		
		public string GetClassName(TableSchema table)
		{
			return GetClassName(table.Name);
		}	
		
		public string GetClassName(string name)
		{
			return StringUtil.ToPascalCase( name.Replace("_"," " ));
		}
		
		public string GetBaseClassName(TableSchema table)
		{
			return GetClassName(table) + "Base";
		}
		public bool IsManyToManyTable(TableSchema table)
		{
			// if Columns.Count = ForeignKey.Count = 2 then ManyToMany
			if (table.Columns.Count == 2 
				&& table.ForeignKeys.Count == 2)
			{
				return true;
			}

			
			return false;
		}	
		
		public string GetOneToManyName(TableKeySchema key)
		{
			Trace.WriteLine("Getting One-To-Many name for key: " + key.Name);
			int numberOfSimilarKeys = 0;
			Trace.WriteLine("Finding keys for table: " + key.ForeignKeyTable.Name);

			foreach(TableKeySchema otherKey in key.ForeignKeyTable.ForeignKeys)
			{
				if (otherKey.PrimaryKeyTable.Name == key.PrimaryKeyTable.Name)
					numberOfSimilarKeys++;
			}			
			
			Trace.WriteLine("Similar Keys: " + numberOfSimilarKeys);
			
			string relationName = "";
			
			//if this is the only relationship to this type, then use the class name
			//otehrwise, use a name of the column for the field and property
			if (numberOfSimilarKeys == 1)
				relationName = key.ForeignKeyTable.Name;
			else
			{		
				//CheckForCompositeKeys(key);
				
				relationName = StripIDFromName(key.ForeignKeyMemberColumns[0].Name) + "_" + key.ForeignKeyTable.Name;

			} 
			
			return StripIDFromName(relationName);
		
		}		
		
		public ColumnSchema GetManyToOneColumn(TableKeySchema key)
		{
			//CheckForCompositeKeys(key);
			
			return key.ForeignKeyMemberColumns[0];
					
		}
		
		public string GetManyToOneName(TableKeySchema key)
		{
			Trace.WriteLine("Getting Many-To-One name for key: " + key.Name);
			/*
			int numberOfSimilarKeys = 0;
			Trace.WriteLine("Finding keys for table: " + key.ForeignKeyTable.Name);

			foreach(TableKeySchema otherKey in key.ForeignKeyTable.ForeignKeys)
			{
				if (otherKey.PrimaryKeyTable.Name == key.PrimaryKeyTable.Name)
					numberOfSimilarKeys++;
			}			
			
			Trace.WriteLine("Similar Keys: " + numberOfSimilarKeys);
			
			string relationName = "";
			
			if (numberOfSimilarKeys == 1)
				relationName = key.PrimaryKeyTable.Name;
			else
			{
				relationName = GetManyToOneColumn(key).Name;
				
			} */
			
			string relationName = GetManyToOneColumn(key).Name;
			
			return StripIDFromName(relationName);
		
		}
		
		protected string StripIDFromName(string name)
		{
			if (this.StripID == false)
				return name;
				
			if (name.Length > 2)
			{
				//if ID is last two letters, then remove.
				if (name.ToLower().IndexOf("id", name.Length - 2) > 0)
				{
					name = name.Substring(0, name.Length - 2);
				}
			}			
			
			return name;
		}
		
		public string GetManyToOneProperty(TableKeySchema key)
		{
			return this.GetPropertyName( this.GetManyToOneName(key) + "Object");
		}
		
		public string GetManyToOneField(TableKeySchema key)
		{
			return this.GetFieldName( this.GetManyToOneName(key) + "Object");
		}	
		
		public string GetOneToManyProperty(TableKeySchema key)
		{
			return this.GetPropertyName( this.GetOneToManyName(key) + "List");
		}
		
		public string GetOneToManyField(TableKeySchema key)
		{
			return this.GetFieldName( this.GetOneToManyName(key) + "List");
		}			
		
		public TableKeySchemaCollection GetOneToManys(TableSchema primaryTable)
		{
			TableKeySchemaCollection keys = new TableKeySchemaCollection();
			foreach(TableSchema table in this.SourceTables)
			{
				foreach(TableKeySchema foreignKey in table.ForeignKeys)
				{
					if (foreignKey.PrimaryKeyTable == primaryTable)
					{
						if (!this.IsManyToManyTable(table) )
						{
							keys.Add(foreignKey);
						}
					}
				}
			}
			
			return keys;
		}
		
		public TableKeySchemaCollection GetManyToManys(TableSchema primaryTable)
		{
			TableKeySchemaCollection keys = new TableKeySchemaCollection();
			foreach(TableSchema table in this.SourceTables)
			{
				foreach(TableKeySchema foreignKey in table.ForeignKeys)
				{
					if (foreignKey.PrimaryKeyTable == primaryTable)
					{
						if (this.IsManyToManyTable(table) )
						{
							keys.Add(foreignKey);
						}
					}
				}
			}
			
			return keys;			
		}
		
		
		public TableSchema FindOtherTable(TableSchema sourceTable, TableKeySchema manyToManyKey)
		{
			foreach(TableKeySchema foreignKey in manyToManyKey.ForeignKeyTable.ForeignKeys)
			{
				if (foreignKey.PrimaryKeyTable.Name != sourceTable.Name)
				{
					return foreignKey.PrimaryKeyTable;
				}
			}
			
			throw new ApplicationException("Many To Many Table relationship not found.");
		}
		
		public ColumnSchema GetKeyColumn(TableSchema table)
		{
			//CheckForCompositeKeys(table.PrimaryKey);
				
			return table.PrimaryKey.MemberColumns[0];
		}
		
		public bool IsIdentity(ColumnSchema column)
		{
			bool isIdentity = false;			
			if (column.ExtendedProperties.Contains("CS_IsIdentity"))
			{
				isIdentity = (bool)column.ExtendedProperties["CS_IsIdentity"].Value;
			}
			
			return isIdentity;
		}
		
		public bool IsComputed(ColumnSchema column)
		{
			bool isComputed = false;			
			if (column.ExtendedProperties.Contains("CS_IsComputed"))
			{
				isComputed = (bool)column.ExtendedProperties["CS_IsComputed"].Value;
			}
			
			return isComputed;
		}		
		
		public string GetKeyGenerator(ColumnSchema keyColumn)
		{
			
			Trace.WriteLine("----Properties for column: " + keyColumn.Name);
			foreach(ExtendedProperty p in keyColumn.ExtendedProperties)
			{
				Trace.WriteLine(p.Name + " = " + p.Value);
			} 
			
						
			if (this.IsIdentity(keyColumn) )
				return "native";
			
			
			if (keyColumn.SystemType == typeof(Guid) )				
			{
				return "guid";
			}
			
			
			return "assigned";
				
			
		}
		
		public static void CopyDirectory(string Src,string Dst, bool overwrite, bool recursive)
		{
			String[] Files;
	
			if(Dst[Dst.Length-1]!=Path.DirectorySeparatorChar) 
				Dst+=Path.DirectorySeparatorChar;
			if(!Directory.Exists(Dst)) Directory.CreateDirectory(Dst);
			Files=Directory.GetFileSystemEntries(Src);
			foreach(string Element in Files){
				// Sub directories
				if(Directory.Exists(Element) && recursive) 
					CopyDirectory(Element,Dst+Path.GetFileName(Element),overwrite,recursive);
				// Files in directory
				else if (File.Exists(Element) )
				{
					string newFile = Dst+Path.GetFileName(Element);
					if (File.Exists(newFile) == false || overwrite)
						File.Copy(Element,newFile,overwrite);
				}
			}
		}	
		
		public void CheckForCompositeKeys(TableSchema table)
		{
			
			if (table.PrimaryKey.MemberColumns.Count > 1)
			{
				string debugInfo = "Table=" + table.Name + ", Key=" + table.PrimaryKey.Name;
				ThrowCompositeException(debugInfo);
			}
		}
		
		public static void CheckForCompositeKeys(TableKeySchema key)
		{
			
			if (key.ForeignKeyMemberColumns.Count > 2)
			{				
				string debugInfo = "Table=" + key.ForeignKeyTable.Name + ", Key=" + key.Name;
				ThrowCompositeException(debugInfo);
			}			
				

		}
		
		public static void ThrowCompositeException(string debugInfo)
		{
			throw new ApplicationException("Composite keys are not supported except in junction tables. " + debugInfo);

		}
		
		public static bool AllowOrphan(TableKeySchema key)
		{
			CheckForCompositeKeys(key);
			
			if (key.ForeignKeyMemberColumns[0].AllowDBNull == true)
				return true;
			else
				return false;
			
		}
	
	}
	


}